<?php
/**
 * 微信通用操作方法
 * @author Toby.tu 2017-04-12
 */
class WechatFunction{
    /*
        config 配置说明(第一个数值是默认值)
        1、配置正式环境或测试环境
            wechat_cache_service = 1 | 2        -- 1 正式、2 测试
        2、配置存储方式
            wechat_cache_type = redis | db      -- redis 缓存、db 数据库
        3、配置链接方式
            if wechat_cache_type = redis
                配置 wechat_cache_redis_config(格式如下)
            else if wechat_cache_type = db
                配置 wechat_cache_db_config（默认是default，可以在database.php中加新的数据库链接）
                需要创建表来存储缓存数据，请调用createTable() 方法创建表(必须保证已经配置好数据库链接)
        4、配置appkey，这个没有默认值，必须设置
            wechat_cache_appkey = ''            -- 获取ackey的时候使用
        5、配置缓存前缀，非必须
            'wechat_cache_pwd_key'=>'wechatcachekey' -- 如果是redis，防止多个项目缓存互刷
        6、允许使用时临时切换配置(不需要加wechat_cache_)
            $this->setConfig([
                'service' => 2, -- 切换测试环境
                'type' => 'db', -- 切换数据库存储
                'db_config' => 'wechat_cache', -- 切换wechat_cache数据库链接
                'appkey' => '4b19388f58d4f1e3c2d46009bdfa20ca', -- 修改appkey，这样就可以多个公众号使用
                'pwd_key' => 'wechatcachekeydjl', -- 修改缓存前缀
            ]);
    */
    /*
        方法说明：
        getWechatOpenId         获取openid
        getACKey                获取ackey
        getACToken              获取actoken
        getJssdk                获取jssdk
        getCodeKey              根据code获取二维码
        sendWxText              发送文本消息
        sendWxMsg               发送模版消息（微信）
        sendWoaapMsg            发送模版消息（中控）
        sendWxImgText           发送单图文（微信）
        sendWxMutilImgText      发送多图文（微信）
        sendWoaapImgText        发送图文素材（中控）
        get                     获取缓存
        set                     存入缓存
        rm                      删除缓存
        createTable             创建缓存表，可以重复执行
    */
    private $tab = 'wechat_cache';
    private $service_url = [1=> ['url'=>'api.woaap.com','ip'=>'10.20.20.149'],
                                ['url'=>'woaapsh.woaap.com','ip'=>'10.10.6.51:9911']
                            ];//1 正式、2 测试
    private $config = [
                'wechat_cache_redis_config'=>[
                    'host'   => '10.10.6.54',
                    'port'   => 6379,
                ],
                'wechat_cache_db_config'=>'default',
                'wechat_cache_appkey'=>'4b19388f58d4f1e3c2d46009bdfa20ca',
                'wechat_cache_pwd_key'=>'wechatcachekey',//加密标识，最好不同的项目不同的标识
                'wechat_cache_type'=>'redis',//可以改成db 数据库
                'wechat_cache_service'=>'2',//1 正式、2 测试
				'wechat_cache_service_type'=>'ip',//ip: 走域名，url：走外网地址
            ];
	private $setconfig = array();
    public function __construct(){
        # codeing
        $this->ci = &get_instance();
        $this->wx_api = 'https://api.weixin.qq.com/cgi-bin';//微信接口url地址
		$this->init();
    }
	/**
	 * 初始化方法
	 * @author Tujt 2017-04-19
	 */
	private function init() {
		$key = $this->getConfig('wechat_cache_service_type');
		if('ip' == $key){
			$this->woaap_api = $this->getConfig('woaap_api_ip');
		}else{
			$this->woaap_api = $this->getConfig('woaap_api_url');
		}
	}
    /***************************    redis、数据库两种缓存机制实现    *****************************/
    /**
     * 配置config
     * @author Toby.tu 2017-04-12
     */
    public function setConfig($config=[]){
        if(!empty($config)){
            foreach ($config as $key=>$conf) {
                $key = 'wechat_cache_'.$key;
                if(isset($this->config[$key])){
                    $this->config[$key] = $conf;
					$this->setconfig[$key] = 1;
                }
            }
			$this->init();
        }
    }
    /**
     * 获取config配置
     * @author Toby.tu 2017-04-12
     */
    public function getConfig($key=''){
        if(empty($key)) return '';
        if('woaap_api_url' == $key){
            $wechat_cache_service = $this->getConfig('wechat_cache_service');
            return 'http://'.$this->service_url[$wechat_cache_service]['url'];
        }else if('woaap_api_ip' == $key){
            $wechat_cache_service = $this->getConfig('wechat_cache_service');
            return 'http://'.$this->service_url[$wechat_cache_service]['ip'];
        }
		//手动配置过的配置项
		if(isset($this->setconfig[$key])){
			$conf = $this->config[$key];
		}else{
			$conf = config_item($key);
			if(empty($conf)){
				if(isset($this->config[$key]) && !empty($this->config[$key])){
					$conf = $this->config[$key];
				}
			}
		}        
        if(empty($conf)){
            die('配置项['.$key.']未配置。');
        }
        return $conf;
    }
    /**
     * 获取redis对象
     * @author Toby.tu 2017-04-11
     */
    public function getRedis(){
        static $cache_redis_object;//增加静态变量，防止同一个进程多次链接redis
        if(!$cache_redis_object){//初始化redis类
            include_once APPPATH.'libraries/predis/Autoloader.php';
            \Predis\Autoloader::register();
            $redis_host = $this->getConfig('wechat_cache_redis_config');
            $redis_config = array(
                'scheme' => 'tcp',
                'host'   => $redis_host['host'],
                'port'   => $redis_host['port'],
            );
            $cache_redis_object = new \Predis\Client($redis_config);
        }
        return $cache_redis_object;
    }
    /**
     * 获取数据库连接类
     * @author Toby.tu 2017-04-11
     */
    public function getDB(){
        static $cache_dbconnect_object;//增加静态变量，防止同一个进程多次链接数据库
        if(!$cache_dbconnect_object){//初始化redis类
            $db_config = $this->getConfig('wechat_cache_db_config');
            $this->ci->db = $this->ci->load->database($this->db_config,true);//链接数据库
            $cache_dbconnect_object = $this->ci->db;
        }
        return $this->ci->db;
    }
    /**
     * 存入数据
     * @author Toby.tu 2017-04-11
     */
    public function set($key='',$data='$$_cache_&&_data_##',$time=60){
        if(empty($key)) return false;//没有键值
        if('$$_cache_&&_data_##' == $data) return false;//空
        if(!empty($data)) $val = serialize($data);
        else $val = '';
        $key = $this->pwd($key);//对键值进行加密，防止重复
        $wechat_cache_type = $this->getConfig('wechat_cache_type');
        switch ($wechat_cache_type) {
            case 'redis'://redis
                $redis = $this->getRedis();//获取redis对象
                $redis->set($key,$val);
                $redis->expire($key,$time);
                break;
            case 'db'://db
                $param = array();
                $param['cache_key'] = $key;
                $param['cache_data'] = $val;
                $param['time'] = $time;
                $param['create_time'] = date('Y-m-d H:i:s',time());
                $db = $this->getDB();
                $row = $db->select('id')->where('cache_key',$key)->limit(1)->get($this->tab)->row_array();
                if(empty($row)){//插入
                    $db->insert($this->tab,$param);//插入数据库
                }else{//更新
                    $db->where('id',$row['id'])->update($this->tab, $param);
                }
                break;
        }

    }
    /**
     * 获取数据
     * @author Toby.tu 2017-04-11
     */
    public function get($key=''){
        if(empty($key)) return array();
        static $wechat_cache_getdata;//增加静态变量，同一个缓存不多次读取
        $key = $this->pwd($key);//对键值进行加密，防止重复
        if(isset($wechat_cache_getdata[$key])){//缓存已经读取过一次
            return $wechat_cache_getdata[$key];
        }
        $data = '';
        $wechat_cache_type = $this->getConfig('wechat_cache_type');
        switch ($wechat_cache_type) {
            case 'redis'://redis
                $redis = $this->getRedis();//获取redis对象
                $cachedata = $redis->get($key);
                if(!empty($cachedata)) $data = unserialize($cachedata);
                break;
            case 'db'://db
                $db = $this->getDB();
                $row = $db->select('id,cache_data,create_time')->where('cache_key',$key)
                                  ->limit(1)->get($this->tab)->row_array();
                if(!empty($row)){
                    $diff = round(time() - strtotime($row['create_time']));//相差秒数
                    if($diff > $row['time']){//缓存过期
                        $db->where('id',$row['id'])->delete($this->tab);//删除缓存
                    }else{
                        $data = unserialize($row['cache_data']);
                    }
                }
                break;
        }
        return $wechat_cache_getdata[$key] = $data;
    }
    /**
     * 删除缓存
     * @author Toby.tu 2017-04-11
     */
    public function rm($key=''){
        if(!empty($key)){
            $key = $this->pwd($key);//对键值进行加密，防止重复
            $wechat_cache_type = $this->getConfig('wechat_cache_type');
            switch ($wechat_cache_type) {
                case 'redis'://redis
                    $redis = $this->getRedis();//获取redis对象
                    $redis->del($key);//删除缓存
                    break;
                case 'db'://db
                    $db->where('cache_key',$key)->delete($this->tab);//删除缓存
                    break;
            }
        }
    }
    /**
     * 创建缓存表
     * @author Toby.tu 2017-04-13
     */
    public function createTable($prefix=''){
        $sql = "CREATE TABLE IF NOT EXISTS `".$prefix."wechat_cache` (
                  `id` int(11) NOT NULL AUTO_INCREMENT,
                  `cache_key` varchar(32) DEFAULT NULL COMMENT '缓存key',
                  `cache_data` text COMMENT '缓存',
                  `time` int(11) DEFAULT NULL COMMENT '缓存的秒数',
                  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
                  PRIMARY KEY (`id`),
                  KEY `idx_cache_key` (`cache_key`)
                ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;";
        $db = $this->getDB();
        $db->query($sql);
    }
    /***************************    方法整理    *****************************/
    /**
     * 加密字符串
     * @author Toby.tu 2016-12-16
     */
    public function pwd($key=''){
        if(empty($key)) return '';
        $label = $this->getConfig('wechat_cache_pwd_key');
        $label .= 'wechat_cache_pwd_key';
        return md5(sha1($key.$label).sha1($label.$key));
    }
    /**
     * GET 请求
     * @param string $url
     */
    private function http_get($url){
        $oCurl = curl_init();
        if(stripos($url,"https://")!==FALSE){
            curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
            curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, FALSE);
            curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1
        }
        curl_setopt($oCurl, CURLOPT_URL, $url);
        curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 );

        $sContent = curl_exec($oCurl);
        $aStatus = curl_getinfo($oCurl);
        curl_close($oCurl);
        if(intval($aStatus["http_code"])==200){
            return $sContent;
        }else{
            return false;
        }
    }

    /**
     * POST 请求
     * @param string $url
     * @param array $param
     * @param boolean $post_file 是否文件上传
     * @return string content
     */
    private function http_post($url,$param,$post_file=false){
        $oCurl = curl_init();
        if(stripos($url,"https://")!==FALSE){
            curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
            curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, false);
            curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1
        }
        if (is_string($param) || $post_file) {
            $strPOST = $param;
        } else {
            $aPOST = array();
            foreach($param as $key=>$val){
                $aPOST[] = $key."=".urlencode($val);
            }
            $strPOST =  join("&", $aPOST);
        }
        curl_setopt($oCurl, CURLOPT_URL, $url);
        curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 );
        curl_setopt($oCurl, CURLOPT_POST,true);
        curl_setopt($oCurl, CURLOPT_POSTFIELDS,$strPOST);
        $sContent = curl_exec($oCurl);
        $aStatus = curl_getinfo($oCurl);
        curl_close($oCurl);
        if(intval($aStatus["http_code"])==200){
            return $sContent;
        }else{
            return false;
        }
    }
    /**
     * 如果文件夹不存在，则递归创建文件夹
     * @param String $path 目录
     * @author Tujt 2013-07-08
     */
    protected function createdir($path){
        if (!file_exists($path)){
            $this->createdir(dirname($path));//递归创建文件夹
            mkdir($path, 0777);
        }
    }
    /**
     * 如果文件不存在，则创建文件
     * @param String $path 目录
     * @author Tujt 2013-07-08
     */
    protected function createfile($route){
        if (!file_exists($route)){
            touch($route);
        }
    }
    /**
     * web端获取或者存储openid
     * @author Toby.tu 2016-09-07
     */
    public function wechatOpenid($appid='',$openid=''){
        if(empty($appid)) return '';
        $key = 'wechat_cache_wechatopenid_key'.$appid;
        $this->ci->load->library('session');
        if(!empty($openid)){
            $this->ci->session->set_userdata($key,$openid);
        }else{//从session中取openid
            $openid = $this->ci->session->userdata($key);
        }
        if(!empty($openid)){
            return $openid;
        }
        return '';
    }
    /**
     * 随机数
     * @author Toby.tu 2017-04-14
     */
    public function createNonceStr($length = 16) {
        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }
    /***************************    微信中控接口封装    *****************************/
    /**
     * 获取openid
     * @author Toby.tu 2017-04-11
     */
    public function getWechatOpenId($appid=''){
        if(empty($appid)) return '';
        $input = $this->ci->input->get();
        $openid = '';
        if (isset($input['code'])){
            $openid = $this->getOpenIdByCode($appid,$input['code']);
            $this->wechatOpenid($appid,$openid);//存入openid
            $callback = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
            $callbackarr = explode('?', $callback);
            header('Location:'.$callbackarr[0]);die;
        }
        $openid = $this->wechatOpenid($appid);//获取当前openid
        if(empty($openid)){//没有openid的话，就跳转
            $this->getOpenIdByWechat($appid);
            exit();
        }
        return $openid;
    }
    /**
     * 根据appid和微信回传的code获取对应的openid
     * @author Toby.tu 2017-04-11
     */
    public function getOpenIdByCode($appid='',$code=''){
        if(empty($appid) || empty($code)) return '';
        $ackey = $this->getACKey($appid);
        if(empty($ackey)){
            die('ackey 为空。');
        }
        $url = $this->woaap_api.'/api/oauth2-accesstoken?ackey='.$ackey.'&code='.$code;
        $jsonInfo = $this->http_get($url);
        $res = json_decode($jsonInfo, true);
        if(!isset($res['openid'])) exit;
        return $res['openid'];
    }
    /**
     * 跳转到获取openid的url地址
     * @author Toby.tu 2017-04-11
     */
    public function getOpenIdByWechat($appid=''){
        if(empty($appid)) return '';
        $url_this = urlencode("http://" . $_SERVER ['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
        $wx_url = $this->woaap_api.'/api/oauth2-authorize?appid='.$appid.'&redirect_uri=' . $url_this . '&scope=snsapi_base';
        header('Location:'.$wx_url);
    }
    /**
     * 获取公众号ackey
     * @param String $appid 公众号id
     * @param Boolean $f 为true时，主动获取ackey，忽略静态变量和缓存
     * @return String 获取appid对应的ackey，缓存时间1分钟
     * @author Toby.tu 2016-08-04
     */
    public function getACKey($appid='',$f=false) {
        if(empty($appid)) return '';
        static $static_wechat_cache_ackey;
        if(isset($static_wechat_cache_ackey[$appid]) && !$f){
            return $static_wechat_cache_ackey[$appid];
        }
        $cachekey = 'wechat_cache_ackey_'.$appid;
        $ackey = $this->get($cachekey);
        if(empty($ackey) || $f){//允许重新获取ackey
            $ackey = '';
            $appkey = $this->getConfig('wechat_cache_appkey');
            $url = $this->woaap_api."/api/ackey?appid=".$appid."&appkey=".$appkey;
            $jsoninfo = $this->http_get($url);
            $result = json_decode($jsoninfo,true);
            if(!empty($result) && isset($result['ackey'])){
                $ackey = $result['ackey'];
                $this->set($cachekey,$ackey,60);//一分钟取一次
            }
        }
        return $static_wechat_cache_ackey[$appid] = $ackey;
    }
    /**
     * 获取公众号accesstoken
     * @param String $appid 公众号id
     * @param Boolean $f 为true时，主动获取token，忽略静态变量和缓存
     * @return String 获取appid对应的token，缓存时间1分钟
     * @author Toby.tu 2016-08-04
     */
    public function getACToken($appid='',$f=false){
        if(empty($appid)) return '';
        static $static_wechat_cache_actoken;
        if(isset($static_wechat_cache_actoken[$appid]) && !$f){
            return $static_wechat_cache_actoken[$appid];
        }
        $key = 'wechat_account_token_'.$appid;
        $ac = $this->get($key);
        if(!$ac || $f){//直接重新获取token
            $ac = '';
            $ackey = $this->getACKey($appid);
            if(empty($ackey)){
                die('ackey 为空。');
            }
            $url = $this->woaap_api.'/api/accesstoken?ackey='.$ackey;
            $result = $this->http_get($url);
            if(!empty($result)){
                $arr  = json_decode($result,true);
                $ac = $arr['access_token'];
            }
            $this->set($key,$ac,60);
        }
        return $static_wechat_cache_actoken[$appid] = $ac;
    }
    /**
     * 获取jssdk
     * @param String $appid 公众号id
     * @param Boolean $f 为true时，主动获取jssdk，忽略静态变量和缓存
     * @return Array jssdk，页面上直接放 wx.config() 方法里就可以初始化wx对象
     * @author Toby.tu 2017-04-12
     */
    public function getJssdk($appid='',$f=false){
        if(empty($appid)) return '';
        static $static_wechat_cache_jssdkconfig;
        if(isset($static_wechat_cache_jssdkconfig[$appid])){
            return $static_wechat_cache_jssdkconfig[$appid];
        }
        $key = 'woaap_djl_wechat_jssdkConfig'.$appid;
        $jssdkconfigarr = $this->get($key);
        if(!$jssdkconfigarr || $f){
            $jssdkconfigarr = array();
            $timestamp  = time();
            $idx = 0;$js_ticket = array();
            do {
                $idx ++;
                if($idx > 3) break;//至多循环三次
                if($idx > 1){
                    sleep(1);//如果失败，则尝试三次，但每次延迟1秒
                    $ackey = $this->getACKey($appid,true);
                }else{
                    $ackey = $this->getACKey($appid);
                }
                if(empty($ackey)){
                    continue;
                }
                $url = $this->woaap_api.'/api/jsticket?ackey='.$ackey;
                $result = $this->http_get($url);
                if(empty($result)){
                    continue;
                }
                $result = json_decode($result,true);
                if(0 == $result['errcode']){//获取成功
                    $js_ticket = $result['js_ticket'];
                    break;
                }
                if(400002 != $result['errcode']){//40002 ackey不正确
                    break;
                }
                $idx ++;
                if($idx > 3){//不允许超过三次循环
                    break;
                }
                sleep(1);//如果失败，则尝试三次，但每次延迟1秒
            } while (true);
            if(!empty($js_ticket)){
                // 注意 URL 一定要动态获取，不能 hardcode.
                if(empty($local)){
                    $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
                    $local = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
                }else{
                    $local = urldecode($local);
                }
                $nonceStr = $this->createNonceStr();
                // 这里参数的顺序要按照 key 值 ASCII 码升序排序
                $string = "jsapi_ticket={$js_ticket}&noncestr={$nonceStr}&timestamp={$timestamp}&url={$local}";
                $signPackage = array(
                    "debug"   =>false,
                    "appId"     => $appid,
                    "nonceStr"  => $nonceStr,
                    "timestamp" => $timestamp,
                    "signature" => sha1($string),
                    "jsApiList" =>array(
                        "onMenuShareTimeline",
                        "onMenuShareAppMessage",
                        "onMenuShareQQ",
                        "onMenuShareWeibo",
                        "onMenuShareQZone",
                        "startRecord",
                        "stopRecord",
                        "onVoiceRecordEnd",
                        "playVoice",
                        "pauseVoice",
                        "stopVoice",
                        "onVoicePlayEnd",
                        "uploadVoice",
                        "downloadVoice",
                        "chooseImage",
                        "previewImage",
                        "uploadImage",
                        "downloadImage",
                        "translateVoice",
                        "getNetworkType",
                        "openLocation",
                        "getLocation",
                        "hideOptionMenu",
                        "showOptionMenu",
                        "hideMenuItems",
                        "showMenuItems",
                        "hideAllNonBaseMenuItem",
                        "showAllNonBaseMenuItem",
                        "closeWindow",
                        "scanQRCode",
                        "chooseWXPay",
                        "openProductSpecificView",
                        "addCard",
                        "chooseCard",
                        "openCard"
                    )
                );
                $jssdkconfigarr = $signPackage;
                $this->set($key,$jssdkconfigarr,60);//缓存
            }
        }
        return $jssdkconfigarr;
    }
    /**
     * jssdk调用微信扫码后，通过code获取对应的二维码
     * @param String $code 微信返回的code
     * @return String code对应的二维码
     * @author Toby.tu 2016-08-23
     */
    public function getCodeKey($code=''){
        if(empty($code)) return '';
        $url = $this->woaap_api.'/api-sys/qrcode-select?url='.urlencode($code);
        $rest_json = $this->http_get($url);
        if(empty($rest_json)){
            return '';
        }
        $rest_arr = json_decode($rest_json,true);
        $code = $rest_arr['code'];
        if(empty($code)){
            return '';
        }
        return $code;
    }
    /**
     * 发送单条消息
     * @param String $appid 公众号id
     * @param String $openid 粉丝id
     * @param String $text 要发送的消息
     * @return Array(
     *      errcode:0,-- 0是成功，其他是失败
     * ) 也可能为空，为空也是失败
     * 通过微信接口直接发送单条消息，只从中控拿token，并有失败自处理机制
     * 当token失效的时候，会重新主动获取token，并尝试三次发送单条消息
     * @author Toby.tu 2017-04-13
     */
    public function sendWxText($appid='',$openid='',$text=''){
        if(empty($appid) || empty($openid) || empty($text)){
            return array();
        }
        $idx = 0;
        do {//如果token不正确，则重新获取token发送消息
            $idx ++;
            if($idx > 3) break;//至多循环三次
            if($idx > 1){
                sleep(1);//如果失败，则尝试三次，但每次延迟1秒
                $token = $this->getACToken($appid,true);//直接获取token，刷新token
            }else{
                $token = $this->getACToken($appid);//直接缓存中的token
            }
            $url= $this->wx_api.'/message/custom/send?access_token='.$token;
            $data = array();
            $data['touser'] = $openid;
            $data['msgtype'] = 'text';
            $data['text'] = ['content'=>$text];
            $result = $this->http_post($url,json_encode($data,JSON_UNESCAPED_UNICODE));
            if(empty($result)) continue;
            $res = json_decode($result,true);
            if(0 == $res['errcode']){//发送成功
                break;
            }
            # 非token问题不再重发
            if(40001 != $res['errcode']){//token不正确
                break;
            }
        } while (true);
        if(!empty($res)){
            return $res;
        }
        return array();
    }
    /**
     * 发送模版消息(中控)
     * @param String $appid 公众号id
     * @param String $openid 粉丝id
     * @param Array $wxmsg 要发送的消息
     *              template_id : 模版id
     *              url : 链接，非必选
     *              first : 标题
     *              keyword1 : 第一行字本
     *              keyword2 : 第二行文本
     *              ...... 可以无限多个
     *              remark : 备注
     *              color : 文字颜色，非比选
     * @return Array(
     *      errcode:0,-- 0是成功，其他是失败
     * ) 也可能为空，为空也是失败
     * 调用中控接口，发送对应模版的模版消息
     * @author Toby.tu 2017-04-13
     */
    public function sendWxMsg($appid='',$openid='',$wxmsg=[]){
        if(empty($appid) || empty($openid) || empty($wxmsg)) return array();
        $msgdata = array();
        $msgdata['touser'] = $openid;
        $msgdata['template_id'] = $wxmsg['tpl_id'];
        if(isset($wxmsg['url'])){
            $msgdata['url'] = $wxmsg['url'];
        }
        $color = '#173177';//文字颜色
        if(isset($wxmsg['color'])){
            $color = $wxmsg['color'];
        }
        $msgdata['data']['first']['value'] = $wxmsg['first'];
        $msgdata['data']['first']['color'] = $color;
        foreach ($wxmsg as $fld=>$msg) {
            if(substr($fld,0,7) == 'keyword'){
                $msgdata['data'][$fld]['value'] = $msg;
                $msgdata['data'][$fld]['color'] = $color;
            }
        }
        if(isset($wxmsg['remark'])){
            $msgdata['data']['remark']['value'] = $wxmsg['remark'];
            $msgdata['data']['remark']['color'] = $color;
        }
        $idx = 0;
        do {
            $idx ++;
            if($idx > 3) break;//至多循环三次
            if($idx > 1){
                sleep(1);//如果失败，则尝试三次，但每次延迟1秒
                $token = $this->getACToken($appid,true);//直接获取token，刷新token
            }else{
                $token = $this->getACToken($appid);//直接缓存中的token
            }
            $url = $this->wx_api.'/message/template/send?access_token='.$token;
            $result = $this->http_post($url,json_encode($msgdata,JSON_UNESCAPED_UNICODE));
            // die;
            if(empty($result)) continue;
            $res = json_decode($result,true);
            if(0 == $res['errcode']){//发送成功
                break;
            }
            # 非token问题不再重发
            if(40001 != $res['errcode']){//token不正确
                break;
            }
        } while (true);
        if(!empty($res)){
            return $res;
        }
        return array();
    }
    /**
     * 发送模版消息(中控)
     * @param String $appid 公众号id
     * @param String $openid 粉丝id
     * @param Array $wxmsg 要发送的消息
     *              template_id : 模版id
     *              url : 链接，非必选
     *              first : 标题
     *              keyword1 : 第一行字本
     *              keyword2 : 第二行文本
     *              ...... 可以无限多个
     *              remark : 备注
     *              color : 文字颜色，非比选
     * @return Array(
     *      errcode:0,-- 0是成功，其他是失败
     * ) 也可能为空，为空也是失败
     * 调用中控接口，发送对应模版的模版消息
     * @author Toby.tu 2017-04-13
     */
    public function sendWoaapMsg($appid='',$openid='',$wxmsg=[]){
        if(empty($appid) || empty($openid) || empty($wxmsg)) return false;
        $msgdata = array();
        $msgdata['touser'] = $openid;
        $msgdata['template_id'] = $wxmsg['tpl_id'];
        if(isset($wxmsg['url'])){
            $msgdata['url'] = $wxmsg['url'];
        }
        $color = '#173177';//文字颜色
        if(isset($wxmsg['color'])){
            $color = $wxmsg['color'];
        }
        $msgdata['data']['first']['value'] = $wxmsg['first'];
        $msgdata['data']['first']['color'] = $color;
        foreach ($wxmsg as $fld=>$msg) {
            if(substr($fld,0,7) == 'keyword'){
                $msgdata['data'][$fld]['value'] = $msg;
                $msgdata['data'][$fld]['color'] = $color;
            }
        }
        if(isset($wxmsg['remark'])){
            $msgdata['data']['remark']['value'] = $wxmsg['remark'];
            $msgdata['data']['remark']['color'] = $color;
        }
        $url = $this->woaap_api.'/api/message-template-send?appid='.$appid;
        $rs = $this->http_post($url,json_encode($msgdata,JSON_UNESCAPED_UNICODE));
        return $rs;
    }
    /**
     * 发送单图文(微信)
     * @param String $appid 公众号id
     * @param String $openid 粉丝id
     * @param Array $param 单图文结果集
     *              title : 标题
     *              des : 备注
     *              link : 链接
     *              picurl : 图片地址
     * @return Array(
     *      errcode:0,-- 0是成功，其他是失败
     * ) 也可能为空，为空也是失败
     * 通过微信接口直接发送单图文，只从中控拿token，并有失败自处理机制
     * 当token失效的时候，会重新主动获取token，并尝试三次发送单图文
     * @author Toby.tu 2017-04-13
     */
    public function sendWxImgText($appid='',$openid='',$param=array()){
        if(empty($appid) || empty($openid) || empty($param)){
            return array();
        }
        $idx = 0;
        do {
            $idx ++;
            if($idx > 3) break;//至多循环三次
            if($idx > 1){
                sleep(1);//如果失败，则尝试三次，但每次延迟1秒
                $token = $this->getACToken($appid,true);//直接获取token，刷新token
            }else{
                $token = $this->getACToken($appid);//直接缓存中的token
            }
            //组装数组
            $contentarr = array();
            if(isset($param['title'])){
                $contentarr['title'] = $param['title'];
            }else{
                $contentarr['title'] = '';
            }
            if(isset($param['des'])){
                $contentarr['description'] = $param['des'];
            }else{
                $contentarr['description'] = '';
            }
            if(isset($param['url'])){
                $contentarr['url'] = $param['url'];
            }else{
                $contentarr['url'] = '';
            }
            if(isset($param['picurl'])){
                $contentarr['picurl'] = $param['picurl'];
            }else{
                $contentarr['picurl'] = '';
            }
            //发送单图文
            $data = array();
            $data['touser'] = $openid;
            $data['msgtype'] = 'news';
            $data['news'] = ['articles'=>[$contentarr]];
            $url = $this->wx_api.'/message/custom/send?access_token='.$token;
            $result = $this->http_post($url,json_encode($data,JSON_UNESCAPED_UNICODE));
            if(empty($result)) continue;
            $res = json_decode($result,true);
            if(0 == $res['errcode']){//发送成功
                break;
            }
            # 非token问题不再重发
            if(40001 != $res['errcode']){//token不正确
                break;
            }
        } while (true);
        if(!empty($res)){
            return $res;
        }
        return array();
    }
    /**
     * 发送多图文(微信)
     * @param String $appid 公众号id
     * @param String $openid 粉丝id
     * @param Array $param 单图文结果集
     *      (
     *              [title : 标题
     *              des : 备注
     *              link : 链接
     *              picurl : 图片地址]
     *              ...
     *      )
     * @return Array(
     *      errcode:0,-- 0是成功，其他是失败
     * ) 也可能为空，为空也是失败
     * 通过微信接口直接发送单图文，只从中控拿token，并有失败自处理机制
     * 当token失效的时候，会重新主动获取token，并尝试三次发送单图文
     * @author Toby.tu 2017-04-13
     */
    public function sendWxMutilImgText($appid='',$openid='',$params=array()){
        if(empty($appid) || empty($openid) || empty($params)){
            return array();
        }
        $idx = 0;
        do {
            $idx ++;
            if($idx > 3) break;//至多循环三次
            if($idx > 1){
                sleep(1);//如果失败，则尝试三次，但每次延迟1秒
                $token = $this->getACToken($appid,true);//直接获取token，刷新token
            }else{
                $token = $this->getACToken($appid);//直接缓存中的token
            }
            //组装数组
            $contentarr = array();
            foreach ($params as $param) {
                $content = array();
                if(isset($param['title'])){
                    $content['title'] = $param['title'];
                }else{
                    $content['title'] = '';
                }
                if(isset($param['des'])){
                    $content['description'] = $param['des'];
                }else{
                    $content['description'] = '';
                }
                if(isset($param['url'])){
                    $content['url'] = $param['url'];
                }else{
                    $content['url'] = '';
                }
                if(isset($param['picurl'])){
                    $content['picurl'] = $param['picurl'];
                }else{
                    $content['picurl'] = '';
                }
                $contentarr[] = $content;
            }
            //发送单图文
            $data = array();
            $data['touser'] = $openid;
            $data['msgtype'] = 'news';
            $data['news'] = ['articles'=>$contentarr];
            $url = $this->wx_api.'/message/custom/send?access_token='.$token;
            $result = $this->http_post($url,json_encode($data,JSON_UNESCAPED_UNICODE));
            if(empty($result)) continue;
            $res = json_decode($result,true);
            if(0 == $res['errcode']){//发送成功
                break;
            }
            # 非token问题不再重发
            if(40001 != $res['errcode']){//token不正确
                break;
            }
        } while (true);
        if(!empty($res)){
            return $res;
        }
        return array();
    }
    /**
     * 发送图文素材，需传入media_id
     * @param String $appid 公众号id
     * @param String $openid 粉丝id
     * @param Integer $media_id 素材id
     * @return Array(
     *      errcode:0,-- 0是成功，其他是失败
     * ) 也可能为空，为空也是失败
     * 根据素材id，把对应的素材以多图文形式发给粉丝
     * @author Toby.tu 2017-04-13
     */
    public function sendWoaapImgText($appid='',$openid='',$media_id=0){
        if(empty($appid) || empty($openid) || empty($media_id)){
            return array();
        }
        $url = $this->woaap_api.'/material-guest/send-service-msg';
        $post = array(
            'appid'=>$appid,
            'material_id'=>$media_id,
            'openid'=>$openid
        );
        $result = $this->http_post($url,$post);
        if(empty($result)){
            return array();
        }
        $res = json_decode($return,true);
        if(0 == $res['errcode']){
            return $res;
        }
        return array();
    }
	/**
     * 根据appid和openid，获取对应的用户信息
     * @author Toby.tu 2016-12-16
     */
    public function getWechatUserByOpenid($appid='',$openids=array()){
        if(empty($appid) || empty($openids)){
            return array();
        }
        $useropenid = array();
        foreach ($openids as $openid) {
            $useropenid['user_list'][] = array('openid'=>$openid,'lang'=>'zh_CN');
        }
        $idx = 0;$user_list = array();
        do {//拉取四次数据
            $idx ++;
            if($idx > 3) break;//至多循环三次
            if($idx > 1){
                sleep(1);//如果失败，则尝试三次，但每次延迟1秒
                $token = $this->getACToken($appid,true);//直接获取token，刷新token
            }else{
                $token = $this->getACToken($appid);//直接缓存中的token
            }
			//$token .= 1;
			$url = $this->wx_api.'/user/info/batchget?access_token='.$token;
            $userinfo = $this->http_post($url,json_encode($useropenid,JSON_UNESCAPED_UNICODE));
            if(!empty($userinfo)){//获取不到数据
                //循环解锁数据，直到解锁成功为止
                $user_list = json_decode($userinfo,true);//解析数据
                if(empty($user_list)){
                    $userinfo = substr(str_replace('\"','"',json_encode($userinfo)),1,-1);
                    $user_list = json_decode($userinfo,true);//解析数据
                }
				if(isset($user_list['errcode']) && 40001 == $user_list['errcode']){
					continue;
				}
                break;//不管是否解锁成功，已经从微信获取到数据
            }
        } while (true);
        if(isset($user_list['user_info_list']) && !empty($user_list['user_info_list'])){
            return $user_list['user_info_list'];
        }
        return array();
    }
}
?>
